/*
  1. Promise 就是一个类 在执行这个类的时候 需要传递一个执行器进去 执行器会立即执行
  2. Promise 中有三种状态 分别为 成功 fulfilled 失败 rejected 等待 pending
    pending -> fulfilled
    pending -> rejected
    一旦状态确定就不可更改
  3. resolve和reject函数是用来更改状态的
    resolve: fulfilled
    reject: rejected
  4. then方法内部做的事情就判断状态 如果状态是成功 调用成功的回调函数 如果状态是失败 调用失败回调函数 then方法是被定义在原型对象中的
  5. then成功回调有一个参数 表示成功之后的值 then失败回调有一个参数 表示失败后的原因
  6. 同一个promise对象下面的then方法是可以被调用多次的
  7. then方法是可以被链式调用的, 后面then方法的回调函数拿到值的是上一个then方法的回调函数的返回值
  8. statics 
    race 返回第一个成功或者失败的 promise 结果, 
    all 返回所有的成功或者第一个失败的 结果, 
    any 返回第一个成功或者所有失败的 结果, 
    resolve , reject
  9. prototype catch, then, finally
*/

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    static race(promises) {
        return new Promise((res, rej) => {
            for (let index = 0; index < promises.length; index++) {
                const element = promises[index];
                element()
                    .then((data) => res(data))
                    .catch((err) => rej(err));
            }
        });
    }
    static all(promises) {
        return new Promise((res, rej) => {
            let resCount = 0,
                datas = [];
            for (let index = 0; index < promises.length; index++) {
                const element = promises[index];
                element()
                    .then((data) => {
                        datas[index] = data;
                        if (resCount === promises.length - 1) {
                            res(datas);
                            return;
                        }
                        resCount++;
                    })
                    .catch((err) => {
                        rej(err);
                    });
            }
        });
    }
    static any(promises) {
        return new Promise((res, rej) => {
            let rejCount = 0,
                rejError = {};
            for (let index = 0; index < promises.length; index++) {
                const element = promises[index];
                element()
                    .then((data) => res(data))
                    .catch((err) => {
                        rejError[index] = err;
                        if (rejCount === promises.length - 1) {
                            rej(rejError);
                            return;
                        }
                        rejCount++;
                    });
            }
        });
    }
    static resolve(value) {
        if (value instanceof MyPromise) {
            return value;
        }
        return new MyPromise((res) => res(value));
    }

    static reject(value) {
        if (value instanceof MyPromise) {
            return value;
        }
        return new MyPromise((res, rej) => rej(value));
    }

    _fulfilledValue = undefined;
    _rejectedValue = undefined;
    _onFulFilleds = [];
    _onRejecteds = [];
    status = PENDING;

    _resolve(fulfilledValue) {
        if (this.status !== PENDING) {
            throw new Error(`Promise be ${this.status}`);
        }
        this.status = FULFILLED;
        this._fulfilledValue = fulfilledValue;
        for (let index = 0; index < this._onFulFilleds.length; index++) {
            this._onFulFilleds[index](this._fulfilledValue);
        }
    }

    _reject(rejectedValue) {
        if (this.status !== PENDING) {
            throw new Error(`Promise be ${this.status}`);
        }
        this.status = REJECTED;
        this._rejectedValue = rejectedValue;
        for (let index = 0; index < this._onRejecteds.length; index++) {
            this._onRejecteds[index](this._rejectedValue);
        }
    }

    constructor(exector) {
        exector(this._resolve.bind(this), this._reject.bind(this));
    }

    then(onFulFilled, onRejected) {
        onFulFilled =
            onFulFilled instanceof Function ? onFulFilled : (value) => value;
        onRejected =
            onFulFilled instanceof Function ? onRejected : (value) => value;

        const nextPromise = new MyPromise((res, rej) => {
            switch (this.status) {
                case PENDING:
                    this._onFulFilleds.push(() => {
                        setTimeout(() => {
                            try {
                                const value = onFulFilled(this._fulfilledValue);
                                this.nextResolve(nextPromise, value, res, rej);
                            } catch (error) {
                                rej(error);
                            }
                        });
                    });
                    this._onRejecteds.push(() => {
                        setTimeout(() => {
                            try {
                                const value = onRejected(this._rejectedValue);
                                this.nextResolve(nextPromise, value, res, rej);
                            } catch (error) {
                                rej(error);
                            }
                        });
                    });
                    break;
                case FULFILLED:
                    setTimeout(() => {
                        try {
                            const value = onFulFilled(this._fulfilledValue);
                            this.nextResolve(nextPromise, value, res, rej);
                        } catch (error) {
                            rej(error);
                        }
                    });
                    break;
                case REJECTED:
                    setTimeout(() => {
                        try {
                            const value = onRejected(this._fulfilledValue);
                            this.nextResolve(nextPromise, value, res, rej);
                        } catch (error) {
                            rej(error);
                        }
                    });
                    break;
                default:
                    break;
            }
        });
        return nextPromise;
    }

    catch(onRejected) {
        return this.then(undefined, onRejected);
    }

    finally(onCallBack) {
        return this.then(
            (value) => new MyPromise.resolve(onCallBack()).then(() => value),
            (error) =>
                new MyPromise.resolve(onCallBack()).then(() => {
                    throw error;
                })
        );
    }

    nextResolve(nextPromise, value, res, rej) {
        if (nextPromise === value) {
            return rej(
                new TypeError('Chaining cycle detected for promise #<Promise>')
            );
        }

        if (value instanceof MyPromise) {
            value.then(res, rej);
        }

        res(value);
    }
}

module.exports = MyPromise;

const test = () => {
    return new MyPromise((res, rej) => {
        setTimeout(() => {
            // res('success');
            rej('error');
        }, 10);
    });
};

const test2 = () => {
    return new MyPromise((res, rej) => {
        setTimeout(() => {
            res('success2');
            // rej('error');
        }, 1000);
    });
};

MyPromise.any([test, test2]).then((data) => {
    console.log(data);
});

// test()
//     .then((data) => {
//         console.log(data);
//         return data;
//     })
//     .then(() => {
//         return 'asdf';
//     })
//     .then((data) => {
//         console.log(data);
//     })
//     .finally((data) => {
//         console.log(data);
//     });
